package roborally.Pieces;

import be.kuleuven.cs.som.annotate.*;

/**
 * This is a class representing a certain amount of energy in a certain unit of energy.
 * @note 	This class is not a value class: energies can be in- and decreased
 * @note 	This class is implemented nominally, so many methods have preconditions
 * @note 	Precondition this.getEnergyUnit() == energy.getEnergyUnit() is not added to each method (would limit changes to future interface), 
 * 			because it is the responsibility of this class to convert energy unit.
 * 
 * @invar 	this.getAmount() >= 0
 * @version 3.0
 * @author 	Bart Opsomer 2de Bachelor Ingenieurswetenschappen Electrotechniek-Computerwetenschappen 
 * 		   	Peter Opsomer 1ste Master Wiskundige IngenieursTechnieken
 */
public class Energy implements Comparable<Energy>{
	// the amount of energy in the given unit
	private double amount;
	// the unit of this energy
	private EnergyUnit energyUnit;
	// the zero energy
	public static final Energy ZERO = new Energy(0.0);
	
	/**
	 * Make an Energy in the given unit of Energy with the given value.
	 * 
	 * @pre amount >= 0
	 * @param amount the amount of energy in this Energy
	 * @param energyUnit the unit of this Energy
	 * @post | new.getAmount() == amount
	 * @post | new.getEnergyUnit() == energyUnit
	 */
	@Model @Raw
	public Energy(double amount, EnergyUnit energyUnit) {
		assert(amount >= 0);
		this.amount = amount;
		this.energyUnit = energyUnit;
	}
	/**
	 * Make an Energy in Wattseconds with the amount given.
	 * 
	 * @pre amount >= 0
	 * @param amount the amount of energy in Ws for this Energy
	 * @effect | this(amount,EnergyUnit.fromString("Ws"))
	 */
	@Raw
	public Energy(double amount) {
		this(amount,EnergyUnit.fromString("Ws"));
	}
	/**
	 * Copy an Energy to a new Energy.
	 * 
	 * @param 	energy the Energy to be copied
	 * @post 	| new.getAmount() == energy.getAmount()
	 * @post 	| new.getEnergyUnit() == energy.getEnergyUnit()
	 * @throws	NullPointerException
	 * 			| energy == null
	 */
	@Raw
	public Energy(Energy energy) throws NullPointerException{
		this.amount = energy.amount;
		this.energyUnit = energy.energyUnit;
	}
	/**
	 * Increase the amount of energy in this Energy with the given energy. Since amounts of Energy can only be positive, 
	 * there is no need for a precondition on energy.getAmount(). Parameter energy is not adjusted in this method, unless it is this.
	 * 
	 * @param 	energy the Energy to increase this Energy with
	 * @post 	| new.getAmountInWattSeconds() == this.getAmountInWattSeconds() + energy.getAmountInWattSeconds()
	 * @throws	NullPointerException
	 * 			| energy == null || this.energyUnit == null
	 */
	public void increase(final Energy energy) throws NullPointerException{
		amount += ( energy.getAmountInWattSeconds()/this.energyUnit.getAmountInWattSeconds() );
	}
	/**
	 * Decrease the amount of energy in this Energy with the given energy. Parameter energy is not adjusted in this method, unless it is this.
	 * @pre this.isGreaterThanOrEquals(energy)
	 * @param energy the Energy to decrease this Energy with
	 * @post | new.getAmountInWattSeconds() == this.getAmountInWattSeconds() - energy.getAmountInWattSeconds()
	 * @throws	NullPointerException
	 * 			| energy == null || this.energyUnit == null
	 */
	public void decrease(final Energy energy) throws NullPointerException{
		assert(this.getAmountInWattSeconds() >= energy.getAmountInWattSeconds());
		amount -= ( energy.getAmountInWattSeconds()/this.energyUnit.getAmountInWattSeconds() );
	}
	/**
	 * @return the energy represented by this object in WattSeconds
	 * @throws	NullPointerException
	 * 			| this.energyUnit == null
	 */
	@Basic
	public double getAmountInWattSeconds() throws NullPointerException{
		return amount*this.energyUnit.getAmountInWattSeconds();
	}
	/**
	 * Divide the amount of energy in this Energy with the given energy. Parameter energy is not adjusted in this method, unless it is this.
	 * @pre	energy is not zero
	 * 		|energy.compareTo(Energy.ZERO) > 0 
	 * @param energy the Energy to divide this Energy with
	 * @post | result == this.getAmountInWattSeconds()/energy.getAmountInWattSeconds()
	 * @throws	NullPointerException
	 * 			| this.energyUnit == null || energy == null  || energy.energyUnit == null
	 */
	public double divide(Energy energy) throws NullPointerException{
		assert(energy.compareTo(Energy.ZERO) > 0);
		return getAmountInWattSeconds()/energy.getAmountInWattSeconds();
	}
	/**
	 * Check whether this Energy is greater than or equal to the given energy.
	 * 
	 * @param 	energy the Energy to compare this Energy with
	 * @return 	| this.getAmountInWattSeconds() >= energy.getAmountInWattSeconds()
	 * @throws 	NullPointerException
	 * 			| energy == null || this.energyUnit == null || energy.energyUnit == null
	 */
	public boolean isGreaterThanOrEquals(Energy energy) throws NullPointerException{
		return this.getAmountInWattSeconds() >= energy.getAmountInWattSeconds();
	}
	/**
	 * Return a String representation of this Energy containing the amount of energy and the unit.
	 * 
	 * @return | result.equals(this.getAmount() + " " + this.getEnergyUnit().getText() )
	 * @throws 	NullPointerException
	 * 			| this.energyUnit == null
	 */
	public String toString() throws NullPointerException{
		return amount + " " + energyUnit.getText();
	}
	/**
	 * Compare this Energy with the given Energy.
	 * 
	 * @param other the Energy to compare this Energy with
	 * @return a positive number if this Energy is greater than the given Energy, negative if lower and 0 if they represent the same quantity
	 * 			| sign( getAmountInWattSeconds() - other.getAmountInWattSeconds() )
	 * @throws	NullPointerException
	 * 			| other == null  || this.energyUnit == null || other.energyUnit == null
	 */
	@Override
	public int compareTo(Energy other) throws NullPointerException{
		if(getAmountInWattSeconds() < other.getAmountInWattSeconds()) {
			return -1;
		}
		else if(getAmountInWattSeconds() > other.getAmountInWattSeconds()){
			return +1;
		}
		return 0;
	};
	/**
	 * @return the energy Unit of this Energy
	 */
	@Basic
	public EnergyUnit getEnergyUnit() {
		return energyUnit;
	}
	public enum EnergyUnit {WATTSECONDS("Ws") {
		@Override
		public int getAmountInWattSeconds() {
			return 1;
		}
	},JOULE("Joule") {
		@Override
		public int getAmountInWattSeconds() {
			return 1;
		}
	},KILOJOULE("KiloJoule") {
		@Override
		public int getAmountInWattSeconds() {
			return 1000;
		}
	};
		// the textual representation of the energyUnit
		private String text;
		/**
		 * Make a new Energy Unit from a String, only used to initialize this enum, so no exceptions if the EnergyUnit doesn't exist yet
		 * @param unit the String to make the EnergyUnit from
		 * @post | new.getText().equals(unit)
		 */
		@Raw
		EnergyUnit(String unit) throws IllegalStateException{
			this.text = unit;
		}
		/**
		 * @return the text of this Energy Unit
		 */
		@Basic
		public String getText() {
			return this.text;
		}
		/**
		 * Get the Energy Unit that the given String represents.
		 * 
		 * @param unit the String representing an Energy Unit
		 * @return | result == null || result.getText().equals(unit)
		 */
		public static EnergyUnit fromString(String unit) {
			if (unit != null) {
				for (EnergyUnit b : EnergyUnit.values()) {
					if (unit.equals(b.getText())) {
						return b;
					}
				}
			}
			return null;
		}
		public abstract int getAmountInWattSeconds();
	}
}
